﻿using System;
using System.Text;
using Adaptive.Agrona;
using Adaptive.Agrona.Concurrent;

#pragma warning disable CS8618
namespace Unity.Type.BinaryType
{
    public sealed class MetadataE
    {
        public const int BlockLength = 68;
        public const int TemplateId = 1;
        public const int SchemaId = 1;
        public const int SchemaVersion = 0;
        public const string SemanticType = "";
        public const ByteOrder DefaultByteOrder = ByteOrder.LittleEndian;

        private readonly MetadataE _parentMessage;
        private IMutableDirectBuffer _buffer;
        private int _offset;
        private int _limit;
        private int _actingBlockLength;
        private int _actingVersion;

        public int Offset => _offset;
        
        private static MessageHeader defaultMessageHeader = new MessageHeader();

        public MetadataE()
        {
            _parentMessage = this;
            if (_buffer == null)
            {
                IMutableDirectBuffer objectBuffer = new UnsafeBuffer(new byte[BlockLength + MessageHeader.Size]);
                WrapForEncodeAndApplyHeader(objectBuffer, 0, defaultMessageHeader);
            }
        }

        public void WrapForEncode(IMutableDirectBuffer buffer, int offset)
        {
            _buffer = buffer;
            _offset = offset;
            _actingBlockLength = BlockLength;
            _actingVersion = SchemaVersion;
            Limit = offset + _actingBlockLength;
        }

        public void WrapForEncodeAndApplyHeader(IMutableDirectBuffer buffer, int offset, MessageHeader headerEncoder)
        {
            headerEncoder.Wrap(buffer, offset, SchemaVersion);
            headerEncoder.BlockLength = BlockLength;
            headerEncoder.TemplateId = TemplateId;
            headerEncoder.SchemaId = SchemaId;
            headerEncoder.Version = SchemaVersion;

            WrapForEncode(buffer, offset + MessageHeader.Size);
        }

        public void WrapForDecode(IMutableDirectBuffer buffer, int offset, int actingBlockLength, int actingVersion)
        {
            _buffer = buffer;
            _offset = offset;
            _actingBlockLength = actingBlockLength;
            _actingVersion = actingVersion;
            Limit = offset + _actingBlockLength;
        }

        public int Size => _limit - _offset;

        public int Limit
        {
            get => _limit;
            set
            {
                _buffer.CheckLimit(value);
                _limit = value;
            }
        }

        public const int UnityIdLength = CsiIdLength + DelimiterLength + SpecVersionLength +
                                         RandomLength + CounterLength;
                                         

        public const int CsiIdId = 1;
        public const int CsiIdSinceVersion = 0;
        public const int CsiIdDeprecated = 0;
        public bool CsiIdInActingVersion()
        {
            return _actingVersion >= CsiIdSinceVersion;
        }

        public const int CsiIdOffset = 0;

        public static string CsiIdMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte CsiIdNullValue = 0;
        public const byte CsiIdMinValue = 32;
        public const byte CsiIdMaxValue = 126;

        public const int CsiIdLength = 8;

        public byte GetCsiId(int index)
        {
            if ((uint)index >= 8)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            return _buffer.GetByte(_offset + 0 + (index * 1));
        }

        public void SetCsiId(int index, byte value)
        {
            if ((uint)index >= 8)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            _buffer.PutByte(_offset + 0 + (index * 1), value);
        }

        public ReadOnlySpan<byte> CsiId
        {
            get => CsiIdAsSpan();
            set => value.CopyTo(_buffer.ByteArray.AsSpan(_offset + 0, CsiIdLength));
        }

        public Span<byte> CsiIdAsSpan()
        {
            byte[] csiIdData = new byte[CsiIdLength];
            _buffer.GetBytes(_offset + 0, csiIdData, 0, CsiIdLength);
            return new Span<byte>(csiIdData);
        }

        public const string CsiIdCharacterEncoding = "ASCII";
        public readonly static Encoding CsiIdResolvedCharacterEncoding = Encoding.GetEncoding(CsiIdCharacterEncoding);
        
        public int GetCsiId(byte[] dst, int dstOffset)
        {
            const int length = 8;
            return GetCsiId(new Span<byte>(dst, dstOffset, length));
        }

        public int GetCsiId(Span<byte> csiIdData)
        {
            const int length = 8;
            if (csiIdData.Length < length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooSmall(csiIdData.Length);
            }

            _buffer.GetBytes(_offset + 0, csiIdData.ToArray());
            return length;
        }

        public void SetCsiId(byte[] src, int srcOffset)
        {
            SetCsiId(new ReadOnlySpan<byte>(src, srcOffset, src.Length - srcOffset));
        }

        public void SetCsiId(ReadOnlySpan<byte> csiIdData)
        {
            const int length = 8;
            if (csiIdData.Length > length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooLarge(csiIdData.Length);
            }

            _buffer.PutBytes(_offset + 0, csiIdData.ToArray());
        }

        public void SetCsiId(string value)
        {
            MetadataHelper.SetNullTerminatedBytesFromString(_buffer, value, _offset + 0, CsiIdLength, CsiIdNullValue);
        }
        public string GetCsiId()
        {
            return MetadataHelper.GetStringFromNullTerminatedBytes(_buffer, _offset + 0, CsiIdLength, CsiIdNullValue);
        }

        public const int DelimiterId = 2;
        public const int DelimiterSinceVersion = 0;
        public const int DelimiterDeprecated = 0;
        public bool DelimiterInActingVersion()
        {
            return _actingVersion >= DelimiterSinceVersion;
        }

        public const int DelimiterOffset = 8;

        public static string DelimiterMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte DelimiterNullValue = 0;
        public const byte DelimiterMinValue = 32;
        public const byte DelimiterMaxValue = 126;

        public const int DelimiterLength = 1;

        public byte Delimiter
        {
            get => _buffer.GetByte(_offset + 8);
            set => _buffer.PutByte(_offset + 8, value);
        }


        public const int SpecVersionId = 3;
        public const int SpecVersionSinceVersion = 0;
        public const int SpecVersionDeprecated = 0;
        public bool SpecVersionInActingVersion()
        {
            return _actingVersion >= SpecVersionSinceVersion;
        }

        public const int SpecVersionOffset = 9;

        public static string SpecVersionMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte SpecVersionNullValue = 0;
        public const byte SpecVersionMinValue = 32;
        public const byte SpecVersionMaxValue = 126;

        public const int SpecVersionLength = 1;

        public byte SpecVersion
        {
            get => _buffer.GetByte(_offset + 9);
            set => _buffer.PutByte(_offset + 9, value);
        }


        public const int RandomId = 4;
        public const int RandomSinceVersion = 0;
        public const int RandomDeprecated = 0;
        public bool RandomInActingVersion()
        {
            return _actingVersion >= RandomSinceVersion;
        }

        public const int RandomOffset = 10;

        public static string RandomMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch: 
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence: return "required";
                default:
                    return "";
            }
        }

        public const byte RandomNullValue = 0;
        public const byte RandomMinValue = 32;
        public const byte RandomMaxValue = 126;

        public const int RandomLength = 19;

        public byte GetRandom(int index)
        {
            if ((uint)index >= 19)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            return _buffer.GetByte(_offset + 10 + (index * 1));
        }

        public void SetRandom(int index, byte value)
        {
            if ((uint)index >= 19)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            _buffer.PutByte(_offset + 10 + (index * 1), value);
        }

        public ReadOnlySpan<byte> Random
        {
            get => RandomAsSpan();
            set => value.CopyTo(_buffer.ByteArray.AsSpan(_offset + 10, RandomLength));
        }

        public Span<byte> RandomAsSpan()
        {
            byte[] randomData = new byte[RandomLength];
            _buffer.GetBytes(_offset + 10, randomData, 0, RandomLength);
            return new Span<byte>(randomData);
        }

        public const string RandomCharacterEncoding = "ASCII";
        public readonly static Encoding RandomResolvedCharacterEncoding = Encoding.GetEncoding(RandomCharacterEncoding);
        
        public int GetRandom(byte[] dst, int dstOffset)
        {
            const int length = 19;
            return GetRandom(new Span<byte>(dst, dstOffset, length));
        }

        public int GetRandom(Span<byte> randomData)
        {
            const int length = 19;
            if (randomData.Length < length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooSmall(randomData.Length);
            }

            _buffer.GetBytes(_offset + 10, randomData.ToArray());
            return length;
        }

        public void SetRandom(byte[] src, int srcOffset)
        {
            SetRandom(new ReadOnlySpan<byte>(src, srcOffset, src.Length - srcOffset));
        }

        public void SetRandom(ReadOnlySpan<byte> randomData)
        {
            const int length = 19;
            if (randomData.Length > length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooLarge(randomData.Length);
            }

            _buffer.PutBytes(_offset + 10, randomData.ToArray());
        }

        public void SetRandom(string value)
        {
            MetadataHelper.SetNullTerminatedBytesFromString(_buffer, value, _offset + 10, RandomLength, RandomNullValue);
        }
        public string GetRandom()
        {
            return MetadataHelper.GetStringFromNullTerminatedBytes(_buffer, _offset + 10, RandomLength, RandomNullValue);
        }

        public const int CounterId = 5;
        public const int CounterSinceVersion = 0;
        public const int CounterDeprecated = 0;
        public bool CounterInActingVersion()
        {
            return _actingVersion >= CounterSinceVersion;
        }

        public const int CounterOffset = 29;

        public static string CounterMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte CounterNullValue = 0;
        public const byte CounterMinValue = 32;
        public const byte CounterMaxValue = 126;

        public const int CounterLength = 5;

        public byte GetCounter(int index)
        {
            if ((uint)index >= 5)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            return _buffer.GetByte(_offset + 29 + (index * 1));
        }

        public void SetCounter(int index, byte value)
        {
            if ((uint)index >= 5)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            _buffer.PutByte(_offset + 29 + (index * 1), value);
        }

        public ReadOnlySpan<byte> Counter
        {
            get => CounterAsSpan();
            set => value.CopyTo(_buffer.ByteArray.AsSpan(_offset + 29, CounterLength));
        }

        public Span<byte> CounterAsSpan()
        {
            byte[] counterData = new byte[CounterLength];
            _buffer.GetBytes(_offset + 29, counterData, 0, CounterLength);
            return new Span<byte>(counterData);
        }

        public const string CounterCharacterEncoding = "ASCII";
        public readonly static Encoding CounterResolvedCharacterEncoding = Encoding.GetEncoding(CounterCharacterEncoding);
        
        public int GetCounter(byte[] dst, int dstOffset)
        {
            const int length = 5;
            return GetCounter(new Span<byte>(dst, dstOffset, length));
        }

        public int GetCounter(Span<byte> counterData)
        {
            const int length = 5;
            if (counterData.Length < length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooSmall(counterData.Length);
            }

            _buffer.GetBytes(_offset + 29, counterData.ToArray());
            return length;
        }

        public void SetCounter(byte[] src, int srcOffset)
        {
            SetCounter(new ReadOnlySpan<byte>(src, srcOffset, src.Length - srcOffset));
        }

        public void SetCounter(ReadOnlySpan<byte> counterData)
        {
            const int length = 5;
            if (counterData.Length > length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooLarge(counterData.Length);
            }

            _buffer.PutBytes(_offset + 29, counterData.ToArray());
        }

        public void SetCounter(string value)
        {
            MetadataHelper.SetNullTerminatedBytesFromString(_buffer, value, _offset + 29, CounterLength, CounterNullValue);
        }
        public string GetCounter()
        {
            return MetadataHelper.GetStringFromNullTerminatedBytes(_buffer, _offset + 29, CounterLength, CounterNullValue);
        }

        public const int TimestampId = 6;
        public const int TimestampSinceVersion = 0;
        public const int TimestampDeprecated = 0;
        public bool TimestampInActingVersion()
        {
            return _actingVersion >= TimestampSinceVersion;
        }

        public const int TimestampOffset = 34;

        public static string TimestampMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const long TimestampNullValue = -9223372036854775808L;
        public const long TimestampMinValue = -9223372036854775807L;
        public const long TimestampMaxValue = 9223372036854775807L;

        public long Timestamp
        {
            get => _buffer.GetLong(_offset + 34, DefaultByteOrder);
            set => _buffer.PutLong(_offset + 34, value, DefaultByteOrder);
        }


        public const int VersionId = 7;
        public const int VersionSinceVersion = 0;
        public const int VersionDeprecated = 0;
        public bool VersionInActingVersion()
        {
            return _actingVersion >= VersionSinceVersion;
        }

        public const int VersionOffset = 42;

        public static string VersionMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const long VersionNullValue = -9223372036854775808L;
        public const long VersionMinValue = -9223372036854775807L;
        public const long VersionMaxValue = 9223372036854775807L;

        public long Version
        {
            get => _buffer.GetLong(_offset + 42, DefaultByteOrder);
            set => _buffer.PutLong(_offset + 42, value, DefaultByteOrder);
        }


        public const int ChangerCsiIdId = 8;
        public const int ChangerCsiIdSinceVersion = 0;
        public const int ChangerCsiIdDeprecated = 0;
        public bool ChangerCsiIdInActingVersion()
        {
            return _actingVersion >= ChangerCsiIdSinceVersion;
        }

        public const int ChangerCsiIdOffset = 50;

        public static string ChangerCsiIdMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const int ChangerCsiIdNullValue = -2147483648;
        public const int ChangerCsiIdMinValue = -2147483647;
        public const int ChangerCsiIdMaxValue = 2147483647;

        public int ChangerCsiId
        {
            get => _buffer.GetInt(_offset + 50, DefaultByteOrder);
            set => _buffer.PutInt(_offset + 50, value, DefaultByteOrder);
        }


        public const int EntityTypeId = 9;
        public const int EntityTypeSinceVersion = 0;
        public const int EntityTypeDeprecated = 0;
        public bool EntityTypeInActingVersion()
        {
            return _actingVersion >= EntityTypeSinceVersion;
        }

        public const int EntityTypeOffset = 54;

        public static string EntityTypeMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public EntityType EntityType
        {
            get => (EntityType)_buffer.GetByte(_offset + 54);
            set => _buffer.PutByte(_offset + 54, (byte)value);
        }


        public const int EventTypeId = 10;
        public const int EventTypeSinceVersion = 0;
        public const int EventTypeDeprecated = 0;
        public bool EventTypeInActingVersion()
        {
            return _actingVersion >= EventTypeSinceVersion;
        }

        public const int EventTypeOffset = 55;

        public static string EventTypeMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public EventType EventType
        {
            get => (EventType)_buffer.GetByte(_offset + 55);
            set => _buffer.PutByte(_offset + 55, (byte)value);
        }


        public const int FormatVerId = 11;
        public const int FormatVerSinceVersion = 0;
        public const int FormatVerDeprecated = 0;
        public bool FormatVerInActingVersion()
        {
            return _actingVersion >= FormatVerSinceVersion;
        }

        public const int FormatVerOffset = 56;

        public static string FormatVerMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const int FormatVerNullValue = -2147483648;
        public const int FormatVerMinValue = -2147483647;
        public const int FormatVerMaxValue = 2147483647;

        public int FormatVer
        {
            get => _buffer.GetInt(_offset + 56, DefaultByteOrder);
            set => _buffer.PutInt(_offset + 56, value, DefaultByteOrder);
        }


        public const int VersionDeleteId = 12;
        public const int VersionDeleteSinceVersion = 0;
        public const int VersionDeleteDeprecated = 0;
        public bool VersionDeleteInActingVersion()
        {
            return _actingVersion >= VersionDeleteSinceVersion;
        }

        public const int VersionDeleteOffset = 60;

        public static string VersionDeleteMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const long VersionDeleteNullValue = -9223372036854775808L;
        public const long VersionDeleteMinValue = -9223372036854775807L;
        public const long VersionDeleteMaxValue = 9223372036854775807L;

        public long VersionDelete
        {
            get => _buffer.GetLong(_offset + 60, DefaultByteOrder);
            set => _buffer.PutLong(_offset + 60, value, DefaultByteOrder);
        }


        public override string ToString()
        {
            var sb = new StringBuilder(100);
            BuildString(sb);
            return sb.ToString();
        }

        internal void BuildString(StringBuilder builder)
        {
            if (_buffer == null)
            {
                throw new InvalidOperationException("Can't provide string value of null buffer");
            }

            int originalLimit = Limit;
            Limit = _offset + _actingBlockLength;
            builder.Append("[MetadataE](sbeTemplateId=");
            builder.Append(TemplateId);
            builder.Append("|sbeSchemaId=");
            builder.Append(SchemaId);
            builder.Append("|sbeSchemaVersion=");
            if (_parentMessage._actingVersion != SchemaVersion)
            {
                builder.Append(_parentMessage._actingVersion);
                builder.Append('/');
            }
            builder.Append(SchemaVersion);
            builder.Append("|sbeBlockLength=");
            if (_actingBlockLength != BlockLength)
            {
                builder.Append(_actingBlockLength);
                builder.Append('/');
            }
            builder.Append(BlockLength);
            builder.Append("):");

            builder.Append("CsiId=");
            for (int i = 0; i < CsiIdLength && GetCsiId(i) > 0; ++i)
            {
                builder.Append((char)GetCsiId(i));
            }
            builder.Append('|');
            builder.Append("Delimiter=");
            builder.Append(Delimiter);
            builder.Append('|');
            builder.Append("SpecVersion=");
            builder.Append(SpecVersion);
            builder.Append('|');
            builder.Append("Random=");
            for (int i = 0; i < RandomLength && GetRandom(i) > 0; ++i)
            {
                builder.Append((char)GetRandom(i));
            }
            builder.Append('|');
            builder.Append("Counter=");
            for (int i = 0; i < CounterLength && GetCounter(i) > 0; ++i)
            {
                builder.Append((char)GetCounter(i));
            }
            builder.Append('|');
            builder.Append("Timestamp=");
            builder.Append(Timestamp);
            builder.Append('|');
            builder.Append("Version=");
            builder.Append(Version);
            builder.Append('|');
            builder.Append("ChangerCsiId=");
            builder.Append(ChangerCsiId);
            builder.Append('|');
            builder.Append("EntityType=");
            builder.Append(EntityType);
            builder.Append('|');
            builder.Append("EventType=");
            builder.Append(EventType);
            builder.Append('|');
            builder.Append("FormatVer=");
            builder.Append(FormatVer);
            builder.Append('|');
            builder.Append("VersionDelete=");
            builder.Append(VersionDelete);

            Limit = originalLimit;

        }
    }
}
